본문으로 건너뛰기

Server Components as Children of Client Components

  1. The server runs ServerComponent at request time, on the server.
  2. The Client Component is inserted into the Virtual DOM as a client component with its code split into its own js file.
  3. In the Virtual DOM, the Client Component's {children} property is set to the static generated content of the Server Component output.
  4. When the Client Component runs in the borwser, it simply inserts its {children} content.
  5. The Client Component doesn't care whether its {children} began as static content or was generated by RSC!

Client Component doesn't know anything about what's inside of it. It just gets passed a {children} property and can insert the content into its output. It knows nothing about its {children}.

To render itself, Client Component doesn't need to run or render its {children}!

Client Component constructs a box, and is handed what to put in the box via {children}. The creation of the box itself can happen regardless of what's being put in it. The two are independent.

Server component is executed as a Server Component, and it's also executed As A client component.

Don't think of RSC as defining a type of thing. Think of it as the label of an environment where React can be run on a server.

Server Components can be the {children} of Client Components. The Client Component is just a box. We can construct the box without knowing what is going inside it.

Server Component can be async, so they return a Promise. When rendering, RSC will wait for all promises to resolve before returning to html content or virtual DOM back to the browser.

  • Server Components run only on the server and have zero impact on bundle-size. Their code is never downloaded to clients, helping to reduce bundle sizes and improve startup time.
  • Server Components can access server-side data sources, such as databases, files systems, or (micro)services.
  • Server Components seamlessly integrate with Client Components — ie traditional React components. Server Components can load data on the server and pass it as props to Client Components, allowing the client to handle rendering the interactive parts of a page.
  • Server Components can dynamically choose which Client Components to render, allowing clients to download just the minimal amount of code necessary to render a page.
  • 서버 컴포넌트는 다시 로드될 때 클라이언트의 상태를 보존한다. 즉, 서버 컴포넌트 트리가 리프레시될 때 클라이언트 상태, 포커스, 진행 중인 애니메이션까지 중단되거나 재설정되지 않는다.
  • 서버 컴포넌트는 점진적으로 렌더링되며 렌더링된 UI 단위를 클라이언트에 점진적으로 스트리밍한다. 이를 Suspense와 결합하면 개발자가 의도적인 로딩 상태를 만들고 페이지 나머지 부분이 로드될 때까지 기다리는 동안 중요한 콘텐츠를 빠르게 표시할 수 있다.

정리,,

  1. The browser receives the initial HTML from the server, which in fact is an accurate representation of the entire page, just without interactivity. Remember, that HTML was created by executing both RSCs and RCCs on the server, so it is an accurate representation of the page.
  2. When the browser receives the HTML from the server, it immediately creates a DOM representation of it and then paints the page.
  3. Then, React combines the RSC tree with the RCC chunks, thus building a full component tree and a corresponding VDOM. React then updates the browser DOM. This is where I get confused. Why does it need to update the DOM here? Isn't the component tree (i.e. VDOM) it now created on the client exactly the same as the one it created on the server (which it used to build the initial HTML document)?
  4. Finally, hydration happens, which is clear enough.

React components which are written to run only on the server, rather than in the browser. RSC is framework-independent and doesn't depend on new specail methods.

this is React's new line-based internal streaming format. the content is pushed into an array to be processed and split by newlines, which results in the content above, which is the raw react streaming data format.

it's a compact string representation of the virtual DOM, with abbreviations, internal references, and characters with encoded special meanings.

it contains a virtualDOM representation of the static page that was generated.

when routing happens, only the modified parts of the page will be updated (we'll see this later), so it needs to have a virtual representation of the page to dynamically update the DOM

  • Lines are separated with a \n, so this is a line-based format, not JSON, for example
  • the content is actually split into chunks in the source and pushed into an array inside script tags. The above format is only visible after combining the pieces and splitting on \n
  • each line is in the format "ID:TYPE?JSON"
  • Lines can contain references to other lines by using $ID or $LID (ex: $2 or $L2) in their content
  • this format is considered "internal" to react and is not meant for human consumption. You do not need to know how it works to make apps with RSC. But it's useful to see what is happening behind the scenes.

If it needs to update the page, it can compare what it wants with what it knows is there, and perform an efficient update to the DOM. When the page loads, React does a reconciliation between the virtual DOM that it thinks represents the page, and the actual static DOM that the server returned. If any differences are found, it throws a console error. This is because it won't be able to accurately update the DOM if it doesn't have the correct structure representation.

This differs with the previous /pages directory in NextJS, for example, where everything is a client component and you opted into server-side behavior by defining a getServerProps() method etc to control SSR.

By default client components are pre-rendered on the server, and their html is included in the static output. This is called SSR. It's different than RSC.

this reason that client component was split into a seperate js file and inserted as a reference in the virtual DOM is because this is a client component.

this use client directive at the top of the component's source file is what told the bundler and RSC to treat it that way.

As React reconciles the DOM created form the static html with its virtual DOM, it recognizes that part of the static DOM was SSR output from a Client Component.

It knows this because the $L[id] reference in the virtual DOM is in place of the html in the static DOM.

At this point, React renders the client Component. The resulting Client Component html is inserted into the DOM where the static html is currently.

This is called Hydration turning static SSR html into dynamic client-side(CSR) html.

RSC generates both static content and a virtual DOM describing the DOM it generated. After the page loads, the actual DOM is compared to what RSC thinks it rendered. After the page loads, the actual DOM is compared to what RSC thinks it rendered.

Because of the hydration error, React got confused and needed to reconcile the difference between the static content and its virtual DOM. It throws this error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

useEffect does not run in SSR.

'use client'

  • it tells the bundler to output this code as a seperate file with its own url so it can be loaded lazily in the browser.
  • it tells the compiler when this code is needed, it should add code to load the js file for this component.
  • it tells RSC that the virtual DOM it generates should contain a placeholder reference to this client component, rather than the component's html output.

next/dynamic is just a composite of React.lazy() and Suspense

The second options argument to dynamic() set ssr:false to tell the server to skip this component entirely when rendering html.

Can I import a client component into an RSC?

  • Yes. As previously covered, that just tells the bundler to bundle the Client Component source separately and send it to the browser for execution.

Reference